Skip to content

Conversation

@miraserver
Copy link

@miraserver miraserver commented Jan 23, 2026

Summary

This PR contains 12 commits with UI/UX improvements and i18n fixes for the dashboard.

Related Issues:

Changes

Features

  • Expiry countdown badge - Shows days remaining for user mode when <= 7 days
  • Enhanced key statistics modal - Added token details (input/output/cache tokens)
  • Cache hit rate color indicators - Visual feedback in leaderboard (green >= 85%, yellow >= 60%, orange < 60%)
  • i18n: batchEdit translations - Added translations for batch edit functionality

Fixes

  • Group tooltip formatting - Improved tooltip display for user groups
  • Logs table column widths - Better visibility for user column
  • MCP passthrough select - Fixed display of selected value
  • Raw response display - Improved overflow handling
  • Provider test proxy - Use configured proxy for model testing
  • i18n: database connection error - Translated error message
  • i18n: dynamic locale - Replaced hardcoded zh-CN with dynamic locale in charts
  • My Usage page - Various UI improvements

Performance

  • Memoization - Added useMemo for chartConfig and daysLeft calculations

Files Changed

  • Dashboard components (user management, charts, leaderboard, logs)
  • Settings components (providers, data)
  • i18n messages (5 languages)
  • Backend: test-service.ts, repository/key.ts

Testing

  • bun run typecheck - passed
  • bunx biome check - passed for modified files

Checklist

  • Base branch is dev
  • No conflicts with target branch
  • i18n strings added for all 5 languages

Greptile Overview

Greptile Summary

This PR delivers comprehensive dashboard UX improvements and i18n fixes across 49 files, focusing on enhanced token statistics visibility, performance optimizations, and internationalization.

Key Enhancements:

  • Token statistics modal - Redesigned key statistics dialog with detailed token breakdown (input/output/cache tokens), cache hit rate indicators, and improved visual hierarchy using cards instead of tables
  • Expiry countdown badge - User mode now displays days remaining when expiration is ≤7 days, improving visibility of account status
  • Cache hit rate visualization - Leaderboard displays color-coded cache hit rates (green ≥85%, yellow ≥60%, orange <60%) for quick performance assessment
  • Performance optimizations - Memoized chart configurations to prevent unnecessary re-renders
  • Provider testing proxy - Test service now respects configured proxy settings when testing models

i18n Fixes:

  • Replaced hardcoded zh-CN locale with dynamic locale in chart components
  • Added comprehensive translations for new features across 5 languages (en, ja, ru, zh-CN, zh-TW)
  • Translated database connection errors and batch edit functionality

UX Improvements:

  • Adjusted logs table column widths for better user column visibility
  • Enhanced group tooltip formatting with structured lists
  • Improved raw response overflow handling

The changes are well-structured with proper separation between backend data aggregation (repository layer) and frontend presentation (React components). All i18n strings are consistently added across all supported languages.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk - it contains well-isolated UI/UX improvements and i18n additions
  • The changes are primarily focused on UI enhancements and i18n completeness. The code follows existing patterns, includes proper error handling, and adds comprehensive token statistics without modifying core business logic. Backend changes are additive (new token fields) and use COALESCE for null safety. Performance improvements through memoization are appropriate. All modified files passed typecheck and biome checks.
  • No files require special attention - all changes are straightforward and well-tested

Important Files Changed

Filename Overview
src/app/[locale]/dashboard/_components/user/key-stats-dialog.tsx Enhanced modal with token breakdown display (input/output/cache), improved UX with cards instead of table
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx Added expiry countdown badge for user mode (shows days left when ≤7 days), improved group tooltip formatting with structured list
src/repository/key.ts Added token statistics fields (inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens) to support enhanced UI displays
src/lib/provider-testing/test-service.ts Added proxy support for provider testing, uses configured proxy URL when testing models
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx Added color indicators for cache hit rate (green ≥85%, yellow ≥60%, orange <60%) in leaderboard display
src/app/[locale]/dashboard/_components/statistics/chart.tsx Memoized chartConfig and uses dynamic locale for date formatting instead of hardcoded zh-CN

Sequence Diagram

sequenceDiagram
    participant User
    participant Dashboard
    participant KeyStatsDialog
    participant Repository
    participant DB

    User->>Dashboard: View user key statistics
    Dashboard->>Repository: findKeysWithStatistics(userId)
    Repository->>DB: Query message_request with token aggregations
    DB-->>Repository: Return model stats with tokens
    Note over DB,Repository: Includes inputTokens, outputTokens,<br/>cacheCreationTokens, cacheReadTokens
    Repository-->>Dashboard: UserKeyDisplay with modelStats[]
    
    User->>Dashboard: Click key stats button
    Dashboard->>KeyStatsDialog: Open dialog with modelStats
    KeyStatsDialog->>KeyStatsDialog: Calculate totals and cache hit rate
    Note over KeyStatsDialog: Cache hit rate = cacheReadTokens /<br/>(inputTokens + cacheCreationTokens + cacheReadTokens)
    KeyStatsDialog-->>User: Display enhanced modal
    Note over KeyStatsDialog,User: Shows cards with token breakdown,<br/>cache statistics, per-model details

    User->>Dashboard: View leaderboard
    Dashboard->>Dashboard: Render cache hit rates with colors
    Note over Dashboard: Green (≥85%), Yellow (≥60%),<br/>Orange (<60%)
    Dashboard-->>User: Display color-coded leaderboard
Loading

John Doe and others added 12 commits January 22, 2026 16:48
- Remove bold font from model name in usage logs table
- Rename "Quota Usage" to "Quota User Usage"
- Rename "Statistics Summary" to "Key Statistics"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…board charts

- Use useLocale() from next-intl to get current locale
- Replace hardcoded "zh-CN" in toLocaleTimeString/toLocaleDateString/toLocaleString
- Move chartConfig inside component to use translated label
- Add locale to useMemo dependencies where needed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add connectionUnavailable key to all 5 language files
- Use translated message in database-status.tsx when isAvailable is false
- Handle both HTTP 503 errors and status.error from API response

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, the provider model test ignored the configured proxy URL and
always made direct connections. Now the test respects proxyUrl setting
by creating a dispatcher via createProxyAgentForProvider.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tion

- Add providersBatchEdit import to all 5 locale index.ts files
- Update provider group field description to mention select/create behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix MCP passthrough select to show translated label instead of value
- Fix raw response overflow with break-all and overflow-x-hidden

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Shrink Time column (flex 0.8→0.6, min-width 80→56px) and expand User column (flex 0.6→0.8) to show more of the username.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Apply color coding to cache hit rate column: green (>=85%), yellow (60-84%), orange (<60%)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add token statistics (input, output, cache creation, cache read) to SQL queries
- Redesign key stats dialog with summary cards and compact model rows
- Add cache hit rate indicator per model with color coding
- Fix user limit refresh by clearing usage cache on refresh button click
- Add i18n translations for new modal fields (5 languages)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add header and bullet list styling to group tooltips for consistency
with request-filters tooltip design.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Show compact badge with clock icon and days remaining when <= 7 days
- Display tooltip with full localized text on hover
- Rename column header from "Edit" to "Status" in user mode
- Add i18n support for all 5 languages (en, zh-CN, zh-TW, ja, ru)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Wrap chartConfig in useMemo to prevent recreation on every render
- Wrap daysLeft calculation in useMemo with localExpiresAt dependency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

该 PR 为多语言添加代币与缓存相关的翻译键,扩展并返回密钥统计的代币分解字段,重构密钥统计对话框为卡片式布局,引入区域感知的日期/时间格式化,增加数据库连接不可用错误处理,并在提供商测试中添加可选代理支持与相关缓存与 UI 改动。

Changes

Cohort / File(s) 变更总结
通用本地化(common.json)
messages/en/common.json, messages/ja/common.json, messages/ru/common.json, messages/zh-CN/common.json, messages/zh-TW/common.json
为五种语言添加顶级翻译键 status
仪表板本地化(dashboard.json)
messages/en/dashboard.json, messages/ja/dashboard.json, messages/ru/dashboard.json, messages/zh-CN/dashboard.json, messages/zh-TW/dashboard.json
在 leaderboard/keyStatsDialog 中添加 tokens 列,新增 keyStatsDialog.modal(requests、totalTokens、cost、inputTokens、outputTokens、cacheWrite、cacheRead、cacheHitRate、cacheTokens、performanceHigh/Medium/Low)以及 userStatus.daysLeft 复数化文案
使用情况本地化(myUsage.json)
messages/en/myUsage.json, messages/ja/myUsage.json, messages/ru/myUsage.json, messages/zh-CN/myUsage.json, messages/zh-TW/myUsage.json
更新文案:stats.title -> “Key Statistics/密钥统计”等,quotaCollapsible.title -> “User Quota Usage/用户配额使用”等(仅文本修改)
设置本地化(settings/data & index & providers)
messages/*/settings/data.json, messages/*/settings/index.ts, messages/*/settings/providers/form/sections.json
在各语言的 status 下新增 connectionUnavailable 翻译;在各语言 settings/index.ts 中导入并暴露 providers/batchEdit.jsonbatchEdit;调整 provider group 字段描述以支持输入新分组并按 Enter 创建(最多50字符)
仪表板 - 区域感知格式化
src/app/[locale]/dashboard/_components/statistics-chart-card.tsx, .../rate-limit-events-chart.tsx, .../rate-limit-top-users.tsx, .../statistics/chart.tsx
useLocale() 取代硬编码 "zh-CN",将所有日期/时间和小时格式化改为基于当前 locale
仪表板 - 密钥/代币指标与 UI 重构
src/app/[locale]/dashboard/_components/user/key-row-item.tsx, .../key-stats-dialog.tsx, .../user-key-table-row.tsx, .../user-management-table.tsx
modelStats 中新增四个数值字段 inputTokensoutputTokenscacheCreationTokenscacheReadTokens;将 key-stats-dialog 重构为卡片网格并展示代币/缓存/性能指标;添加 daysLeft 计算与到期徽章;在管理表格操作列新增基于 isAdmin 的 status 文案条件渲染
排行榜与表格展示
src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx, src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx, src/app/[locale]/my-usage/_components/usage-logs-table.tsx
leaderboard 中缓存命中率单元格改为返回带颜色的 JSX 元素;调整 logs 表 Time/User 列宽;修改 usage-logs 表模型单元格的字体权重(样式变更)
用户管理与使用缓存
src/app/[locale]/dashboard/users/users-page-client.tsx, src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx
导入并在刷新前调用 clearUsageCache;新增导出函数 clearUsageCache(userId?: number): void 以管理使用缓存;向表格翻译添加 status 操作键
数据库状态处理
src/app/[locale]/settings/data/_components/database-status.tsx
对 HTTP 503 添加专门处理,在解析 JSON 前抛出 connectionUnavailable 翻译错误;渲染不可用状态时使用该翻译
提供商测试服务(代理支持)
src/lib/provider-testing/test-service.ts
executeProviderTest 中加入可选代理:当 config.proxyUrl 存在时创建代理并通过 fetch options 传递,结果中包含新字段 usedProxy?: boolean(成功/错误均返回)
数据层与类型
src/repository/key.ts, src/types/user.ts
KeyStatistics.modelStatsUserKeyDisplay 中加入四个代币字段;更新 findKeysWithStatisticsfindKeysWithStatisticsBatch 查询与映射以选择并返回这些字段

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题准确总结了主要变化,涵盖了UI/UX改进和国际化修复,是对变更集的恰当概括。
Description check ✅ Passed PR描述详细阐述了所有关键变化,包括功能、修复、性能优化和涉及的文件,与变更集高度相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @miraserver, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refines the dashboard's user experience and strengthens its internationalization capabilities. It introduces new features that provide users with more granular insights into their key usage and model performance, while also addressing several UI inconsistencies and improving the overall responsiveness of the application. The changes ensure a more intuitive and globally friendly interface.

Highlights

  • Enhanced Key Statistics Modal: The key statistics modal now provides detailed token information, including input, output, and cache tokens, offering a more comprehensive view of usage per model. It also features a new, visually organized layout with icons and separators.
  • Expiry Countdown Badge: A new badge is displayed for user mode keys that are set to expire within 7 days, providing a clear visual cue for upcoming expirations.
  • Dynamic Locale for Charts: Chart components now dynamically use the current locale for date and time formatting, replacing hardcoded 'zh-CN' and improving internationalization.
  • Cache Hit Rate Indicators: The leaderboard now visually highlights cache hit rates with color indicators: green for >= 85%, yellow for >= 60%, and orange for < 60%, making performance assessment quicker.
  • Provider Test Proxy Support: The provider testing service now utilizes configured proxy settings, ensuring that model tests accurately reflect the production environment's network configuration.
  • Improved i18n and UI Fixes: Numerous internationalization strings have been added or updated across five languages, alongside various UI/UX improvements such as better group tooltip formatting, adjusted logs table column widths, and refined raw response display.
  • Performance Optimization: Memoization has been applied to chart configurations and expiry day calculations to optimize rendering performance.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added enhancement New feature or request area:UI area:i18n area:UX size/M Medium PR (< 500 lines) labels Jan 23, 2026
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR adds UI/UX improvements and i18n fixes to the dashboard. The changes are well-structured with proper i18n support across all 5 languages. However, there is one critical issue that will cause build failure.

PR Size: M

  • Lines changed: 601 (483 additions + 118 deletions)
  • Files changed: 49

Issues Found

Category Critical High Medium Low
Logic/Bugs 1 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

1. Missing batchEdit.json translation files (Build Failure)

  • Location: messages/*/settings/index.ts (lines 93, 210, 327, 447, 566)
  • Issue: All 5 language index.ts files import providersBatchEdit from "./providers/batchEdit.json" but these files do not exist
  • Impact: This will cause TypeScript compilation errors and runtime import failures
  • Files affected:
    • messages/en/settings/index.ts:93
    • messages/ja/settings/index.ts:210
    • messages/ru/settings/index.ts:327
    • messages/zh-CN/settings/index.ts:447
    • messages/zh-TW/settings/index.ts:566
  • Fix: Either create the missing batchEdit.json files in messages/{locale}/settings/providers/ for all 5 languages, OR remove the import/export statements if not needed yet

Review Coverage

  • Logic and correctness - 1 critical issue found
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean (proper error handling in database-status and test-service)
  • Type safety - Clean (proper TypeScript types added for token stats)
  • Documentation accuracy - Clean
  • Test coverage - No new tests required (UI changes only)
  • Code clarity - Good (proper memoization, clear variable names)

Positive Observations

  • Proper i18n implementation across all 5 languages
  • Good use of React.useMemo for performance optimization
  • Proper TypeScript type definitions for new fields
  • Clean error handling with translated messages
  • Proper proxy support added to test-service
  • Cache management with clearUsageCache function

Automated review by Claude AI

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request includes several changes to improve the dashboard and settings pages. It adds a 'status' field to common translations, updates dashboard translations to include tokens and cache-related information, and modifies myUsage translations to be more specific about quota usage. It also adds a new 'batchEdit' feature to provider settings, updates provider group descriptions, and fixes a locale issue in date formatting. Additionally, it enhances the user interface with token statistics, cache hit rate indicators, and improved error handling for database connections. Review comments suggest adding JSDoc comments to the formatTokenAmount function, improving the title attribute for edit/status actions, using constants or utility functions for color classes, adding comments to explain the use of COALESCE in SQL queries, and extracting tooltip content into a separate component.

const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
if (data.resolution === "hour") {
return date.toLocaleTimeString("zh-CN", {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The locale is being hardcoded to zh-CN. It should be dynamically set based on the user's locale.

Suggested change
return date.toLocaleTimeString("zh-CN", {
return date.toLocaleTimeString(locale, {
hour: "2-digit",
minute: "2-digit",
});

const formatTooltipDate = (dateStr: string) => {
const date = new Date(dateStr);
if (data.resolution === "hour") {
return date.toLocaleString("zh-CN", {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The locale is being hardcoded to zh-CN. It should be dynamically set based on the user's locale.

Suggested change
return date.toLocaleString("zh-CN", {
return date.toLocaleString(locale, {
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});

Comment on lines +35 to +45
function formatTokenAmount(tokens: number): string {
if (tokens >= 1_000_000_000) {
return `${(tokens / 1_000_000_000).toFixed(1)}B`;
}
if (tokens >= 1_000_000) {
return `${(tokens / 1_000_000).toFixed(1)}M`;
}
if (tokens >= 1_000) {
return `${(tokens / 1_000).toFixed(1)}K`;
}
return tokens.toLocaleString();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding JSDoc comments to explain the purpose and behavior of the formatTokenAmount function. This will improve code readability and maintainability.

Comment on lines 504 to 510
<div className="px-2 text-center min-w-0">
<span className="block truncate" title={translations.actions.edit}>
{translations.actions.edit}
<span
className="block truncate"
title={isAdmin ? translations.actions.edit : translations.actions.status}
>
{isAdmin ? translations.actions.edit : translations.actions.status}
</span>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To enhance user experience, consider using a more descriptive title attribute that includes the user's name or ID. This would provide more context when hovering over the edit/status action.

                   title={isAdmin ? `${translations.actions.edit} for ${user.name}` : `${translations.actions.status} for ${user.name}`}
                  >

Comment on lines +287 to +296
cell: (row) => {
const rate = Number((row as ProviderCacheHitRateEntry).cacheHitRate || 0) * 100;
const colorClass =
rate >= 85
? "text-green-600 dark:text-green-400"
: rate >= 60
? "text-yellow-600 dark:text-yellow-400"
: "text-orange-600 dark:text-orange-400";
return <span className={colorClass}>{rate.toFixed(1)}%</span>;
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The color classes are hardcoded. Consider defining these color classes as constants or using a utility function to generate them based on the rate value. This would improve maintainability and readability.

        const rate = Number((row as ProviderCacheHitRateEntry).cacheHitRate || 0) * 100;
        const colorClass = rate >= 85 ? "text-green-600 dark:text-green-400" : rate >= 60 ? "text-yellow-600 dark:text-yellow-400" : "text-orange-600 dark:text-orange-400";
        return <span className={colorClass}>{rate.toFixed(1)}%</span>;

Comment on lines 346 to +356
</div>
</TooltipTrigger>
<TooltipContent side="bottom" align="start">
<ul className="text-xs space-y-1">
{userGroups.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
<div className="max-w-xs">
<p className="font-medium mb-1">{translations.keyRow?.fields?.group}:</p>
<ul className="text-xs list-disc list-inside">
{userGroups.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
</div>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider extracting the tooltip content into a separate component to improve readability and maintainability. This would also make it easier to reuse the tooltip content in other parts of the application.

Comment on lines +710 to +712
clearUsageCache();
refetch();
}}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a comment explaining why clearUsageCache is being called before refetch. This would improve code readability and make it easier for other developers to understand the purpose of this code.

            onRefresh={() => {
              // Clear the usage cache to ensure fresh data is fetched
              clearUsageCache();
              refetch();
            }}

Comment on lines +28 to +30
if (response.status === 503) {
throw new Error(t("connectionUnavailable"));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a more specific error message for the 503 status code. This would provide more context to the user and make it easier to troubleshoot the issue.

Suggested change
if (response.status === 503) {
throw new Error(t("connectionUnavailable"));
}
// Use translated message for connection unavailable error
if (response.status === 503) {
throw new Error(t("connectionUnavailable"));
}

Comment on lines 346 to +356
</div>
</TooltipTrigger>
<TooltipContent side="bottom" align="start">
<ul className="text-xs space-y-1">
{userGroups.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
<div className="max-w-xs">
<p className="font-medium mb-1">{translations.keyRow?.fields?.group}:</p>
<ul className="text-xs list-disc list-inside">
{userGroups.map((group) => (
<li key={group}>{group}</li>
))}
</ul>
</div>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider extracting the tooltip content into a separate component to improve readability and maintainability. This would also make it easier to reuse the tooltip content in other parts of the application.

Comment on lines +28 to +30
if (response.status === 503) {
throw new Error(t("connectionUnavailable"));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's good to handle the 503 status code specifically. However, consider adding a more generic error handling block to catch other potential errors and provide a user-friendly message. This would make the error handling more robust.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +187 to +190
const statTotalInput =
stat.inputTokens + stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate =
statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache hit rate calculation includes cacheCreationTokens in the denominator, which may not be correct. Cache hit rate typically measures how often cached data is reused (cache reads) versus fresh data needed (regular input). Including cache creation tokens (which are cache writes) in the total input inflates the denominator.

Consider if the formula should be:

Suggested change
const statTotalInput =
stat.inputTokens + stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate =
statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;
const statTotalInput = stat.inputTokens + stat.cacheReadTokens;
const statCacheHitRate =
statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;

Or alternatively, if you want cache hit rate to be cache reads vs all cache operations:

const statTotalCache = stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate = statTotalCache > 0 ? (stat.cacheReadTokens / statTotalCache) * 100 : 0;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/_components/user/key-stats-dialog.tsx
Line: 187:190

Comment:
Cache hit rate calculation includes `cacheCreationTokens` in the denominator, which may not be correct. Cache hit rate typically measures how often cached data is reused (cache reads) versus fresh data needed (regular input). Including cache creation tokens (which are cache writes) in the total input inflates the denominator.

Consider if the formula should be:
```suggestion
                  const statTotalInput = stat.inputTokens + stat.cacheReadTokens;
                  const statCacheHitRate =
                    statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;
```

Or alternatively, if you want cache hit rate to be cache reads vs all cache operations:
```
const statTotalCache = stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate = statTotalCache > 0 ? (stat.cacheReadTokens / statTotalCache) * 100 : 0;
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@messages/en/myUsage.json`:
- Line 119: The title value "Quota User Usage" is awkward; update the "title"
field in messages/en/myUsage.json (the JSON object's "title" key currently set
to "Quota User Usage") to a clearer phrase such as "User Quota Usage" or "Quota
Usage" by replacing the string value accordingly and ensure surrounding JSON
syntax stays valid.

In `@src/app/`[locale]/dashboard/_components/user/user-key-table-row.tsx:
- Line 15: The countdown badge is shown as "today" when a key is already expired
because the remaining-seconds calculation returns 0; update the remaining-time
logic (e.g., getRemainingSeconds / remainingSeconds computed in the useMemo) to
yield a falsy/negative value or null when the key is expired, and change the
badge rendering (e.g., expiresBadge / renderExpiryBadge or the JSX that shows
the countdown) to only render the countdown when remainingSeconds > 0; ensure
the expired state (isExpired or the expiry icon rendering) still renders when
expired so the expired icon and badge do not conflict.

In `@src/app/`[locale]/settings/data/_components/database-status.tsx:
- Around line 25-31: Check response.status === 503 before calling
response.json() in the error handling branch inside the database-status
component: if status is 503, throw new Error(t("connectionUnavailable"))
immediately; otherwise safely attempt to parse response.json() (wrap in
try/catch) and then throw new Error(errorData?.error || t("error")). Reference
the existing symbols response, response.ok, response.status,
t("connectionUnavailable") and errorData when making the change.

In `@src/lib/provider-testing/test-service.ts`:
- Around line 81-96: The proxy agent creation using createProxyAgentForProvider
(which sets dispatcher and usedProxy) is executed outside the existing try/catch
and can throw, so move the entire proxy creation block into the same try that
builds the ProviderTestResult; catch any exception from
createProxyAgentForProvider and convert it into the structured
ProviderTestResult error path (same classification/fields used for other
failures), ensuring dispatcher is only set on success and usedProxy is updated
accordingly so failures return a proper ProviderTestResult instead of bubbling
an exception.
🧹 Nitpick comments (2)
src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx (1)

79-113: 可选优化:并发请求去重

当多个具有相同 userIdUserLimitBadge 组件同时挂载时,可能会触发重复的 API 请求。可以考虑使用 pending promise 模式进行去重:

♻️ 可选的去重优化方案
// 在文件顶部添加 pending requests 追踪
const pendingRequests = new Map<number, Promise<LimitUsageData | null>>();

// 在 useEffect 中使用
const pending = pendingRequests.get(userId);
if (pending) {
  pending.then((data) => {
    if (data) setUsageData(data);
    else setError(true);
  }).finally(() => setIsLoading(false));
  return;
}

const request = getUserAllLimitUsage(userId).then((res) => {
  // ... existing logic
});
pendingRequests.set(userId, request);
request.finally(() => pendingRequests.delete(userId));
src/app/[locale]/dashboard/_components/statistics/chart.tsx (1)

332-332: 可选改进:数字格式化也传入 locale

为保持一致性,toLocaleString() 用于数字格式化时也可以传入 locale 参数,尽管对于大多数常见语言差异较小。

♻️ 可选的数字格式化统一方案
-                {visibleTotals.calls.toLocaleString()}
+                {visibleTotals.calls.toLocaleString(locale)}

类似地适用于第 358、431、486、588 行的 toLocaleString() 调用。

Also applies to: 358-358

- Move proxy creation into try/catch block (test-service.ts)
- Check 503 status before parsing JSON (database-status.tsx)
- Fix English grammar "User Quota Usage" (myUsage.json)
- Return null for expired keys in getDaysLeft (user-key-table-row.tsx)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@miraserver
Copy link
Author

Addressed PR Review Feedback

Fixed the following issues from CodeRabbit review:

Fixed Issues

# File Issue Status
1 test-service.ts:81-99 Proxy creation outside try/catch Fixed - moved inside try block
2 database-status.tsx:25-31 503 check after response.json() Fixed - check 503 before JSON parse
3 myUsage.json:119 "Quota User Usage" grammar Fixed - changed to "User Quota Usage"
4 user-key-table-row.tsx:114 Badge "0 days" for expired keys Fixed - return null when expTs <= now

Intentionally Not Changed

# File Issue Reason
5 key-stats-dialog.tsx Cache hit rate formula Current formula is correct for "cache utilization" metric
6 Multiple files JSDoc comments Nice-to-have, not critical for this PR
7 user-key-table-row.tsx:356 Extract tooltip component Refactoring scope, not a bug

Commit: adb1d17

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/user/user-key-table-row.tsx:
- Around line 502-522: The daysLeft===0 branch is unreachable given getDaysLeft
returns null or >=1, and the daysLeft > 0 && daysLeft <= 7 check is redundant
because showExpiryBadge already guarantees that range; update the Badge
rendering: remove the daysLeft===0 ternary so variant is either "outline" or
another fixed variant (e.g., keep "outline"), and simplify the className
conditional to only check daysLeft <= 7 (or remove the check entirely and always
apply the small-range styling when showExpiryBadge is true). Change references
in the user-key-table-row rendering (Badge, daysLeft, showExpiryBadge)
accordingly; alternatively, if you intended a 0 state use getDaysLeft to return
0 near expiry instead of changing the UI here.

Comment on lines +502 to +522
) : (
showExpiryBadge && (
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant={daysLeft === 0 ? "destructive" : "outline"}
className={cn(
"text-xs cursor-help",
daysLeft > 0 &&
daysLeft <= 7 &&
"border-amber-500 text-amber-600 dark:text-amber-400"
)}
>
<Clock className="h-3 w-3 mr-1" />
{daysLeft}
</Badge>
</TooltipTrigger>
<TooltipContent>{tUserStatus("daysLeft", { days: daysLeft })}</TooltipContent>
</Tooltip>
)
)}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the user-key-table-row.tsx file to see the full context
wc -l src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx

Repository: ding113/claude-code-hub

Length of output: 137


🏁 Script executed:

# Look for getDaysLeft function in the file
rg "getDaysLeft" -A 15 --context=5 src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx

Repository: ding113/claude-code-hub

Length of output: 934


🏁 Script executed:

# Search for getDaysLeft in other files (it might be imported)
rg "export.*getDaysLeft|function getDaysLeft" -B 2 -A 15

Repository: ding113/claude-code-hub

Length of output: 1961


🏁 Script executed:

# Find where tUserStatus is defined/imported
rg "tUserStatus|const tUserStatus|function tUserStatus" -B 3 -A 3 src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx | head -30

Repository: ding113/claude-code-hub

Length of output: 1175


🏁 Script executed:

# Search for i18n translation files
find . -name "*.json" -path "*/messages/*" -o -name "*.ts" -path "*/i18n*" | grep -E "(messages|i18n)" | head -20

Repository: ding113/claude-code-hub

Length of output: 757


🏁 Script executed:

# Look for dashboard.json or similar translation files
fd -e json -path "*/messages/*" | head -30

Repository: ding113/claude-code-hub

Length of output: 235


🏁 Script executed:

# Find dashboard translation files
find ./messages -name "*dashboard*" -o -name "*userManagement*" -o -name "*userStatus*" | sort

Repository: ding113/claude-code-hub

Length of output: 217


🏁 Script executed:

# List all json files in messages directories
ls -la ./messages/*/

Repository: ding113/claude-code-hub

Length of output: 6775


🏁 Script executed:

# Look for daysLeft in all message files
rg "daysLeft" ./messages/

Repository: ding113/claude-code-hub

Length of output: 661


消除无法触及的代码分支和冗余条件

根据 getDaysLeft 的实现(第 114-121 行),该函数返回 nullMath.ceil(正数) >= 1,永不返回 0。这导致第 507 行的 daysLeft === 0 条件永远为 false"destructive" 变体无法被应用。

同时,showExpiryBadge 的定义已确保 daysLeft 在范围 [1, 7] 内,因此第 510-512 行的 daysLeft > 0 && daysLeft <= 7 在此上下文中恒为 true,属于冗余条件。

建议简化代码:

移除死代码和冗余条件
                  <Badge
-                    variant={daysLeft === 0 ? "destructive" : "outline"}
-                    className={cn(
-                      "text-xs cursor-help",
-                      daysLeft > 0 &&
-                        daysLeft <= 7 &&
-                        "border-amber-500 text-amber-600 dark:text-amber-400"
-                    )}
+                    variant="outline"
+                    className="text-xs cursor-help border-amber-500 text-amber-600 dark:text-amber-400"
                  >

注:翻译文件中包含 =0 的复数形式(如 en 中的 "Expires today"),表明原始设计可能意图支持 daysLeft=0 的场景。若需在最后一天(不足 24 小时)显示 "destructive" 样式,应调整 getDaysLeft 逻辑以在接近过期时返回 0。

🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/_components/user/user-key-table-row.tsx around
lines 502 - 522, The daysLeft===0 branch is unreachable given getDaysLeft
returns null or >=1, and the daysLeft > 0 && daysLeft <= 7 check is redundant
because showExpiryBadge already guarantees that range; update the Badge
rendering: remove the daysLeft===0 ternary so variant is either "outline" or
another fixed variant (e.g., keep "outline"), and simplify the className
conditional to only check daysLeft <= 7 (or remove the check entirely and always
apply the small-range styling when showExpiryBadge is true). Change references
in the user-key-table-row rendering (Badge, daysLeft, showExpiryBadge)
accordingly; alternatively, if you intended a 0 state use getDaysLeft to return
0 near expiry instead of changing the UI here.

@miraserver
Copy link
Author

Re: Cache Hit Rate Formula (key-stats-dialog.tsx:187-190)

The current formula is correct and aligns with Anthropic's official documentation.

Current Implementation

const statTotalInput = stat.inputTokens + stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate = statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;

Why This Is Correct

1. Anthropic's official documentation states:

total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens

Source: Prompt Caching - Tracking cache performance

2. Anthropic's official cookbook uses the same approach:

total_input_tokens = input_tokens + cache_read_input_tokens
percentage_cached = cache_read_input_tokens / total_input_tokens * 100

Source: Prompt Caching Cookbook

Why the Suggested Alternatives Are Less Accurate

Suggested Formula Issue
cacheRead / (input + cacheRead) Excludes cache writes from denominator - artificially inflates hit rate
cacheRead / (cacheCreation + cacheRead) Ignores regular input tokens - measures only cache operations, not total utilization

What Our Formula Measures

Cache Utilization Rate - the percentage of ALL processed input tokens that were served from cache. This is the most meaningful metric for understanding actual cost savings and efficiency.

Conclusion: No changes needed. The current implementation correctly follows Anthropic's token accounting model.

@ding113 ding113 merged commit bf2ada2 into ding113:dev Jan 24, 2026
7 of 10 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:UI area:UX enhancement New feature or request size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants